package org.hepeng.workx.util.proxy;


import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.Proxy;
import javassist.util.proxy.ProxyFactory;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.hepeng.workx.util.ConstructorUtils;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;

/**
 * @author he peng
 */
public class JavassistProxyFactory extends AbstractProxyFactory {

    @Data
    @AllArgsConstructor
    private class JavassistMethodHandler implements MethodHandler {
        private Object nativeObject;
        private List<Invoker> invokers;

        @Override
        public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
            Invocation lastInvocation = new JavassistInvocation(nativeObject , self , thisMethod , args , proceed);
            Invocation invocation = invocationChain(invokers , -1 , lastInvocation);
            Invoker invoker = ivc -> ivc.invoke();
            return invoker.invoke(invocation);
        }
    }


    protected javassist.util.proxy.ProxyFactory getJavassistProxyFactory(
            Class<?> superClassOfProxy , List<Class<?>> interfaces , List<InvokeFilter> filters) {
        javassist.util.proxy.ProxyFactory proxyFactory = new javassist.util.proxy.ProxyFactory();
        proxyFactory.setSuperclass(superClassOfProxy);
        Class<?>[] ifs = new Class[interfaces.size()];
        interfaces.toArray(ifs);
        proxyFactory.setInterfaces(ifs);
        proxyFactory.setFilter(method -> {
            if (CollectionUtils.isNotEmpty(filters)) {
                boolean isProxyInvoke = true;
                for (InvokeFilter filter : filters) {
                    if (Objects.nonNull(filter)) {
                        isProxyInvoke = filter.isProxyInvoke(method);
                    }
                    if (! isProxyInvoke) {
                        return false;
                    }
                }
            }
            return true;
        });
        return proxyFactory;
    }

    @Override
    protected Object doCreateProxyInternal(
            Object target, Class<?> superClassOfProxy, List<Class<?>> interfaces,
            List<Invoker> invokers , List<InvokeFilter> filters) throws Exception {
        ProxyFactory proxyFactory = getJavassistProxyFactory(superClassOfProxy, interfaces , filters);
        Proxy proxy = (Proxy) proxyFactory.create(null, null);
        proxy.setHandler(new JavassistMethodHandler(target , invokers));
        return proxy;
    }


    @Override
    protected Invocation nextInvocation(List<Invoker> invokers, int index, Invocation lastInvocation) {

        Invoker nextInvoker = nextInvoker(invokers, index);
        JavassistInvocation javassistInvocation = (JavassistInvocation) lastInvocation;
        Invocation invocation = new JavassistInvocation(
                javassistInvocation.getNative() , javassistInvocation.getProxy() ,
                javassistInvocation.getMethod() , javassistInvocation.getArgs() ,
                javassistInvocation.getProceedMethod()) {

            @Override
            public Object invoke() throws Throwable {
                return nextInvoker.invoke(invocationChain(invokers , index + 1 , lastInvocation));
            }
        };
        return invocation;
    }

    @Override
    protected Object doCreateProxyInternal(
            Class<?> superClassOfProxy, List<Class<?>> interfaces,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs ,
            List<Invoker> invokers , List<InvokeFilter> filters) throws Exception {

        Object[] args = ConstructorUtils.argToArray(constructorArgs);
        Class<?>[] argTypes = ConstructorUtils.argTypeToArray(constructorArgTypes);
        Object nativeObject = org.apache.commons.lang3.reflect.ConstructorUtils.invokeConstructor(superClassOfProxy, args, argTypes);
        javassist.util.proxy.ProxyFactory proxyFactory = getJavassistProxyFactory(superClassOfProxy , interfaces , filters);
        Proxy proxy = (Proxy) proxyFactory.create(argTypes, args);
        proxy.setHandler(new JavassistMethodHandler(nativeObject , invokers));
        return proxy;
    }

}
